﻿
namespace MagicWatering.Scheduler.Engine
{
    using CGurus.Weather.WundergroundAPI;

    using MagicWatering.Data.DataModel;
    using MagicWatering.Interfaces;
    using MagicWatering.Scheduler.Interfaces;
    using System;
    using System.Diagnostics;
    using System.Threading;

    /// <summary>
    /// Watering program scheduler.
    /// </summary>
    public class WateringProgramScheduler
    {
        private bool m_started;

        private WateringPlan m_plan;
        private readonly Timer s_RunProgramTimer;
        private readonly Timer s_SwitchLineTimer;
        private readonly IWateringController s_Controller;
        
        private WateringProgram m_runningProgram;
        private int m_runningLineIndex;

        /// <summary>
        /// Constructs the scheduler to execute watering programs.
        /// </summary>
        /// <param name="controller">Remote controller, which controls watering lines.</param>
        /// <param name="feedback"></param>
        /// <param name="plan">Watering plan.</param>
        public WateringProgramScheduler(IWateringController controller, IFeedbackService feedback, WateringPlan plan)
        {
            m_started = false;
            s_Controller = controller;
            FeedbackService = feedback;
            // load program
            m_plan = plan;
            s_RunProgramTimer = new Timer(StartProgramByTimer);
            s_SwitchLineTimer = new Timer(SwitchLineByTimer);
            m_runningLineIndex = -1;
        }

        /// <summary>
        /// Starts program scheduler.
        /// </summary>
        public void Start()
        {
            if( !m_started)
            {
                FindAndRunNextProgram();
                m_started = true;
                FeedbackService.Log(Constants.FeedbackClass.Info, "SchedulerStarted");
                return;
            }
            FeedbackService.Log(Constants.FeedbackClass.Info, "SchedulerAlreadyStarted");
        }

        /// <summary>
        /// Stops the program scheduler and the current program execution.
        /// </summary>
        public void Stop()
        {
            if (m_started)
            {
                if( m_runningLineIndex >= 0)
                {
                    StopWateringLine(m_runningLineIndex);
                    m_runningLineIndex = -1;
                }
                m_started = false;
                // stop all timers
                s_RunProgramTimer.Change(Timeout.Infinite, Timeout.Infinite);
                s_SwitchLineTimer.Change(Timeout.Infinite, Timeout.Infinite);
                FeedbackService.Log(Constants.FeedbackClass.Info, "SchedulerStopped");
                return;
            }
            FeedbackService.Log(Constants.FeedbackClass.Info, "SchedulerAlreadyStopped");
        }

        public IFeedbackService FeedbackService
        {
            get;
            private set;
        }

        /// <summary>
        /// Updates watering plan.
        /// </summary>
        /// <param name="plan"></param>
        public void UpdateWateringPlan(WateringPlan plan)
        {
            bool wasStarted = m_started;
            if( wasStarted)
            {
                Stop();
            }
            m_plan = plan;
            if (wasStarted)
            {
                Start();
            }
        }

        private void StartProgramByTimer(object state)
        {
            if( null == m_runningProgram ||  
                null ==  m_runningProgram.WateringLines ||
                m_runningProgram.WateringLines.Count < 1)
            {
                // nothing to control
                FeedbackService.Log(Constants.FeedbackClass.Info, "Nothing to start, invalid configuration");
                return;
            }

            FeedbackService.Log(Constants.FeedbackClass.Info, "Check weather forecast");
            WApi wApi = new WApi("5e14982d6621285b");
            var dayforecast = wApi.GetForecast("DE", "Zirndorf").Forecast.SimpleForecast.ForecastDay[0];
            if (dayforecast.Qpf_Day.Mm > 0 || dayforecast.High.Celsius < 20)
            {
                // watering makes no sence %-)
                return;
            }
            // start the first line
            m_runningLineIndex = 0;
            ActivateWateringLine(m_runningLineIndex);
        }

        private void SwitchLineByTimer(object state)
        {
            m_runningLineIndex++;
            if (m_runningLineIndex >= m_runningProgram.WateringLines.Count)
            {
                // all lines are handled, stop the line 
                StopWateringLine(m_runningLineIndex - 1);

                // finish program and stop timer
                m_runningLineIndex = -1;
                s_SwitchLineTimer.Change(Timeout.Infinite, Timeout.Infinite);
                FeedbackService.Log(Constants.FeedbackClass.Info, "ProgramStopped");
                // find and start new program
                FindAndRunNextProgram();
                return;
            }
            // stop previously started line
            StopWateringLine(m_runningLineIndex - 1);
            // run new line
            ActivateWateringLine(m_runningLineIndex);
        }

        private void ActivateWateringLine(int lineIndex)
        {
            Debug.Assert(null != m_runningProgram.WateringLines);
            WateringLine line = m_runningProgram.WateringLines[lineIndex];
            TimeSpan timeSpan = TimeSpan.FromSeconds(line.SecondsDuration);
            s_SwitchLineTimer.Change((long)timeSpan.TotalMilliseconds, Timeout.Infinite);
            
            // activate the line 
            s_Controller.StartCommunication();
            bool executed =  s_Controller.SetChannelState(line.Channel, true);
            s_Controller.StopCommunication();

            FeedbackService.Log(Constants.FeedbackClass.Info, "LineRunned" + line.Channel);
            Debug.Assert(executed, "SetChannelState to true");
        }

        private void StopWateringLine(int lineIndex)
        {
            Debug.Assert(null != m_runningProgram.WateringLines);

            WateringLine line = m_runningProgram.WateringLines[lineIndex];

            // stop the line 
            s_Controller.StartCommunication();
            bool executed = s_Controller.SetChannelState(line.Channel, false);
            s_Controller.StopCommunication();

            FeedbackService.Log(Constants.FeedbackClass.Info, "LineStopped" + line.Channel);
            Debug.Assert(executed, "SetChannelState to false");
        }

        private void FindAndRunNextProgram()
        {
            TimeSpan waitTime;
            m_runningProgram = m_plan.Program;
            TimeSpan executionTime = m_plan.ExecutionTime.TimeOfDay;
            TimeSpan timeNow = DateTime.Now.TimeOfDay;

            if (timeNow < executionTime)
            {
                // this day
                waitTime = executionTime - timeNow;
            }
            else
            {
                // next day
                waitTime = TimeSpan.FromHours(24) - timeNow + executionTime;
            }

            s_RunProgramTimer.Change((long)waitTime.TotalMilliseconds, Timeout.Infinite);
            FeedbackService.Log(
                Constants.FeedbackClass.Info, 
                string.Format("Next program is executed in {0} sec.", waitTime.TotalSeconds));
        }
    }
}
